package org.swift.commons.xml;

import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.dom4j.Document;
import org.dom4j.io.DocumentResult;
import org.jaxen.JaxenException;

@SuppressWarnings("unchecked")
public class TransformUtils {

	public static OMElement transform(Map parameters, Source source,
			Source styleSheet) throws TransformerException {
		TransformerFactory transformFactory = TransformerFactory.newInstance();
		Transformer transformer = transformFactory.newTransformer(styleSheet);
		DocumentResult result = new DocumentResult();
		setTransformerParameters(parameters, transformer);
		transformer.transform(source, result);
		return convertToAxiom(result.getDocument());
	}

	public static OMElement transform(Source source, Source styleSheet)
			throws TransformerException {
		return transform(Collections.EMPTY_MAP, source, styleSheet);
	}

	public static OMElement transform(OMElement source, OMElement styleSheet)
			throws TransformerException {
		try {
			return transform(omElementToSource(source),
					omElementToSource(styleSheet));
		} catch (JaxenException e) {
			throw new TransformerException(e);
		}
	}

	public static OMElement transform(Map parameters, OMElement source,
			OMElement styleSheet) throws TransformerException {
		try {
			return transform(parameters, omElementToSource(source),
					omElementToSource(styleSheet));
		} catch (JaxenException e) {
			throw new TransformerException(e);
		}
	}

	public static OMElement transform(Map parameters, Templates translet,
			OMElement source) throws TransformerException {
		return convertToAxiom(transformDom4j(parameters, translet, source));
	}

	public static OMElement transform(Templates translet, OMElement source)
			throws TransformerException {
		return convertToAxiom(transformDom4j(translet, source));
	}

	public static Document transformDom4j(Map parameters, Templates translet,
			OMElement source) throws TransformerException {
		Transformer transformer = translet.newTransformer();
		DocumentResult result = new DocumentResult();
		try {
			setTransformerParameters(parameters, transformer);
			transformer.transform(omElementToSource(source), result);
		} catch (JaxenException e) {
			throw new TransformerException(e);
		}
		return result.getDocument();
	}

	public static Document transformDom4j(Templates translet, OMElement source)
			throws TransformerException {
		return transformDom4j(Collections.EMPTY_MAP, translet, source);
	}
	
	public static StreamResult transformToStream(OutputStream os,Map parameters, Source source,
			Source styleSheet) throws TransformerException {
		TransformerFactory transformFactory = TransformerFactory.newInstance();
		Transformer transformer = transformFactory.newTransformer(styleSheet);
		StreamResult result = new StreamResult(os);
		setTransformerParameters(parameters, transformer);
		transformer.transform(source, result);
		return result;
	}

	public static StreamResult transformToStream(OutputStream os,Source source, Source styleSheet)
			throws TransformerException {
		return transformToStream(os,Collections.EMPTY_MAP, source, styleSheet);
	}

	public static StreamResult transformToStream(OutputStream os,OMElement source, OMElement styleSheet)
			throws TransformerException {
		try {
			return transformToStream(os,omElementToSource(source),
					omElementToSource(styleSheet));
		} catch (JaxenException e) {
			throw new TransformerException(e);
		}
	}

	public static OMElement convertToAxiom(Document document) {
		return stringToOMElement(document.asXML());
	}

	public static OMElement stringToOMElement(String text) {
		StAXOMBuilder builder;
		try {
			builder = new StAXOMBuilder(new ByteArrayInputStream(text
					.getBytes()));
		} catch (XMLStreamException e) {
			return null;
		}
		return builder.getDocumentElement();
	}

	public static StreamSource omElementToSource(OMElement sourceXml)
			throws JaxenException {
		String xslt;
		// pull out xslt hax
		// TODO remove this hack when we get projections working
		OMElement firstEl = sourceXml.getFirstElement()!=null ? 
				sourceXml.getFirstElement() : sourceXml;
		if ("Transformer".equals(firstEl.getLocalName())) {

			AXIOMXPath xpathExpression = new AXIOMXPath("stringContent");
			OMElement xsltElement = (OMElement) xpathExpression
					.selectSingleNode(firstEl);

			xslt = xsltElement.getText();

		} else {
			xslt = sourceXml.toString();
		}
		StringReader reader = new StringReader(xslt);
		return new StreamSource(reader);
	}

	private static void setTransformerParameters(Map parameters,
			Transformer transformer) {
		for (Map.Entry entry : (Set<Map.Entry>) parameters.entrySet()) {
			transformer.setParameter(entry.getKey().toString(), entry
					.getValue());
		}
	}
}
